package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestEvalDoComprehensive(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		// Simple counting loop
		{
			"(do ((i 0 (+ i 1))) ((>= i 3) i))",
			[]string{"3"},
		},
		// Loop with body expressions
		{
			"(do ((i 0 (+ i 1))) ((>= i 3) i) (display i))",
			[]string{"3"}, // display prints to stdout but returns NULL_OBJECT, final value is still 3
		},
		// Multiple variables
		{
			"(do ((i 0 (+ i 1)) (sum 0 (+ sum i))) ((>= i 5) sum))",
			[]string{"10"},
		},
		// Factorial calculation
		{
			"(do ((n 5 (- n 1)) (fact 1 (* fact n))) ((= n 1) fact))",
			[]string{"120"},
		},
		// Loop with no result expressions
		{
			"(do ((i 0 (+ i 1))) ((>= i 3)))",
			[]string{""}, // NULL_OBJECT is displayed as empty string
		},
		// Loop with variable but no step (should keep initial value)
		{
			"(do ((x 5)) ((= x 5) x))",
			[]string{"5"},
		},
		// Nested do loops
		{
			"(do ((i 0 (+ i 1))) ((>= i 2) i) (do ((j 0 (+ j 1))) ((>= j 2))))",
			[]string{"2"},
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		for i, expr := range exprs {
			var o obj.Object
			o, err = EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("Input: %s, EvalExpression() error = %v", tt.input, err)
			}

			if o.Inspect() != tt.want[i] {
				t.Errorf("Input: %s, Expression %d: EvalExpression() = %v, want %v", tt.input, i, o.Inspect(), tt.want[i])
			}
		}
	}
}

func TestEvalDoErrorCases(t *testing.T) {
	errorTests := []struct {
		input         string
		expectedError string
	}{
		{
			"(do)",
			"do requires at least bindings and test clause",
		},
		{
			"(do ())",
			"do requires at least bindings and test clause",
		},
		{
			"(do ((i 0)) )",
			"do requires at least bindings and test clause",
		},
		{
			"(do x ((>= i 3) i))",
			"do bindings must be a list",
		},
		{
			"(do ((i 0)) x)",
			"do test clause must be a list",
		},
		{
			"(do ((i)) ((>= i 3) i))",
			"do binding must have 2 or 3 elements: variable, init, and optional step",
		},
		{
			"(do ((i 0 1 2)) ((>= i 3) i))",
			"do binding must have 2 or 3 elements: variable, init, and optional step",
		},
		{
			"(do ((1 0 (+ i 1))) ((>= i 3) i))",
			"do variable must be an identifier",
		},
		{
			"(do ((i undefined-var (+ i 1))) ((>= i 3) i))",
			"identifier not found: undefined-var",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range errorTests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		// Evaluate all expressions
		for _, expr := range exprs {
			_, err = EvalExpression(expr, env)
			if err == nil {
				t.Errorf("Input: %s, expected error but got none", tt.input)
				continue
			}

			if err.Error() != tt.expectedError {
				t.Errorf("Input: %s, expected error %q, got %q", tt.input, tt.expectedError, err.Error())
			}
		}
	}
}
